/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine.cmd.identity;

import cn.easyplatform.dao.DaoException;
import cn.easyplatform.dao.EntityCallback;
import cn.easyplatform.dao.IdentityDao;
import cn.easyplatform.dos.EnvDo;
import cn.easyplatform.dos.OrgDo;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.entities.BaseEntity;
import cn.easyplatform.interceptor.AbstractCommand;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.lang.Times;
import cn.easyplatform.log.LogManager;
import cn.easyplatform.messages.request.ApiInitRequestMessage;
import cn.easyplatform.messages.response.SimpleResponseMessage;
import cn.easyplatform.messages.vos.ApiInitVo;
import cn.easyplatform.messages.vos.RoleVo;
import cn.easyplatform.services.IProjectService;
import cn.easyplatform.services.ISubject;
import cn.easyplatform.type.DeviceType;
import cn.easyplatform.type.IResponseMessage;
import cn.easyplatform.type.StateType;
import cn.easyplatform.util.IdentityUtils;
import cn.easyplatform.util.MessageUtils;
import cn.easyplatform.util.RuntimeUtils;
import cn.easyplatform.util.WxUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.List;

import static cn.easyplatform.dos.UserDo.STATE_LOCK;
import static cn.easyplatform.type.UserType.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ApiInitCmd extends AbstractCommand<ApiInitRequestMessage> {

    private final static Logger log = LoggerFactory.getLogger(ApiInitCmd.class);

    /**
     * @param req
     */
    public ApiInitCmd(ApiInitRequestMessage req) {
        super(req);
    }

    @Override
    public IResponseMessage<?> execute(final CommandContext cc) {
        ApiInitVo av = req.getBody();
        if (log.isInfoEnabled())
            log.info("api auth:{} {} {}", av.getServiceId(), av.getOrgId(), av.getUserId());
        IProjectService projectservice = null;
        for (IProjectService ps : cc.getEngineConfiguration()
                .getProjectServices()) {
            if (ps.getId().equals(av.getServiceId())) {
                projectservice = ps;
                break;
            }
        }
        Subject currentUser = SecurityUtils.getSubject();
        if (projectservice == null)
            return MessageUtils.projectNotFound();
        EnvDo env = new EnvDo(projectservice.getId(), DeviceType.getType(av.getDeviceType()),
                projectservice.getLanguages().get(0), null, null);
        cc.setupEnv(env);
        if (!Strings.isBlank(av.getType())) {
            if (av.getType().contains("wx"))//小程序登陆
                return WxUtil.quick(cc, projectservice, av.getType(), av.getUserId(), av.getNodeIp());
            if (av.getType().contains("phone"))//验证码
                return WxUtil.phone(cc, projectservice, av.getUserId(), av.getUserPass(), av.getNodeIp());
            return new SimpleResponseMessage();
        } else {//密码
            UserDo userInfo = null;
            IdentityDao dao = cc.getIdentityDao(false);
            if (DeviceType.API == env.getDeviceType()) {
                userInfo = dao.getUser(cc.getProjectService().getConfig()
                        .getAuthenticationQuery(), av.getUserId());
                if (userInfo == null || !av.getUserPass().equals(userInfo.getPassword()))
                    return MessageUtils.userNotFound(av.getUserId());
                dao.setUser(userInfo);
                if (userInfo.getType() != TYPE_API)//只能API用户才可以登陆
                    return MessageUtils.userNotFound(av.getUserId());
            } else {
                UsernamePasswordToken token = new UsernamePasswordToken(
                        av.getUserId(), av.getUserPass());
                token.setHost(av.getNodeIp());
                try {
                    currentUser.login(token);
                } catch (AuthenticationException ex) {
                    if (ex instanceof IncorrectCredentialsException) {// 密码不匹配
                        int tryTimes = dao.getLoginFailedTimes(av.getUserId());
                        tryTimes++;
                        dao.updateLoginFailedTimes(av.getUserId(), tryTimes,
                                av.getNodeIp());
                        if (tryTimes >= cc.getProjectService().getConfig().getMaxLoginFailedTimes()) {// 锁用户
                            dao.updateState(av.getUserId(), av.getNodeIp(),
                                    STATE_LOCK);
                            return MessageUtils.userTryLoginFailed(av.getUserId());
                        }
                    }
                    if (log.isWarnEnabled())
                        log.warn("api auth", ex);
                    if (ex.getCause() != null
                            && ex.getCause() instanceof DaoException) {
                        DaoException e = (DaoException) ex.getCause();
                        return MessageUtils.daoAccessError(e.getMessage(),
                                e.getArgs());
                    }
                    return MessageUtils.userNotFound(av.getUserId());
                }
                userInfo = (UserDo) currentUser.getPrincipal();
                dao.setUser(userInfo);
                if (userInfo.getType() == TYPE_ADMIN) {//超级管理员不能在此登陆
                    ((ISubject) currentUser).setAuthenticate(false);
                    return MessageUtils.userNotFound(av.getUserId());
                }
                //检查License
                /*try {
                    cc.getProjectService().validate(userInfo, true);
                } catch (EasyPlatformWithLabelKeyException e) {
                    ((ISubject) currentUser).setAuthenticate(false);
                    throw e;
                }
                int rs = cc.getProjectService().getSessionManager()
                        .checkUser(userInfo);
                if (rs > ONLINE_NONE) {// 表示用户已存在
                    if (userInfo.getType() == TYPE_EXCLUSIVE
                            || userInfo.getType() == TYPE_ADMIN) {
                        ((ISubject) currentUser).setAuthenticate(false);
                        return MessageUtils.userHasLogined(userInfo.getId());
                    }
                    if (userInfo.getType() == TYPE_ONLY) { // 由客户端确认是否要中断已有的用户
                        cc.setUser(userInfo);
                        return MessageUtils
                                .userLoginConfirm(userInfo.getId());
                    }
                }*/
            }
            userInfo.setLocale(projectservice.getLocale().toString());
            userInfo.setPassword(av.getUserPass());
            // 检查用户有效期
            if (userInfo.getValidDate() != null) {
                Date now = Times.toDay();
                if (now.before(userInfo.getValidDate())) { // 设置的有效开始日期还未到
                    ((ISubject) currentUser).setAuthenticate(false);
                    return MessageUtils.userPasswordExpired(av.getUserId());
                }
                if (userInfo.getValidDays() > 0) {// 是否过期
                    int oneDay = 24 * 60 * 60 * 1000;
                    Date when = new Date(userInfo.getValidDate().getTime()
                            + userInfo.getValidDays() * oneDay);
                    if (now.after(when)) {
                        ((ISubject) currentUser).setAuthenticate(false);
                        return MessageUtils.userPasswordExpired(av.getUserId());
                    }
                }
            }
            String query = projectservice.getConfig().getUnitQuery();
            OrgDo org = dao.getUserOrg(query, av.getOrgId());
            if (org == null) {
                ((ISubject) currentUser).setAuthenticate(false);
                return MessageUtils.userNotOrg(av.getOrgId());
            }
            userInfo.setOrg(org);
            userInfo.setLoginDate(new Date());
            userInfo.setLastAccessTime(new Date());
            userInfo.setState(StateType.START);
            userInfo.setDeviceType(env.getDeviceType());
            //userInfo.setTimeout(cc.getProjectService().getConfig().getTimeToLive());
            Object body;
            if (DeviceType.API.getName().equals(av.getDeviceType())) {
                ((ISubject) currentUser).setAuthenticate(true);
                //currentUser.getSession().setTimeout(-1);//会话永不过期
                body = currentUser.getSession().getId();
            } else {
                EntityCallback<BaseEntity> cb = new EntityCallback<BaseEntity>() {
                    @Override
                    public BaseEntity getEntity(String entityId) {
                        return cc.getEntity(entityId);
                    }

                    public String getLabel(String code) {
                        return RuntimeUtils.getLabel(cc, code, null);
                    }
                };
                List<RoleVo> roles = null;
                if (projectservice.getConfig().getStrictMode() == 1) {
                    roles = dao.getUserRoles(userInfo.getOrg().getId(), userInfo.getId(), cc
                            .getEnv().getDeviceType().getName(), cb);
                } else {
                    if (projectservice.getConfig().getStrictMode() == 2)
                        roles = dao.getUserOrgRoles(userInfo.getOrg().getId(), userInfo.getId(), cc.getEnv().getDeviceType().getName(), cb);
                    if (roles == null || roles.isEmpty())
                        roles = dao.getUserRoles(userInfo.getId(), cc.getEnv()
                                .getDeviceType().getName(), cb);
                }
                userInfo.setRoles(roles);
                body = roles;
                ((ISubject) currentUser).setAuthenticate(true);
            }
            userInfo.setIp(av.getNodeIp());
            cc.setUser(userInfo);
            IdentityUtils.log(cc, dao);
            LogManager.startUser(cc.getEngineConfiguration().getLogPath(), projectservice.getId(), userInfo);
            return new SimpleResponseMessage(body);
        }
    }
}
